Sub

Which Linux distribution you use?













Custom IPTables extensions

Custom IPTables extensions

Jarosław Sajko

Applying system security policy to firewall rules can be a difficult task, especially if it requires functionality that the basic firewall product does not provide. Fortunately, if the firewall is based on IPTables, you can implement the required functionality yourself by writing an extension module. What’s more, you’ll be surprised just how easy it is.

Anyone even vaguely familiar with the Internet and computers must have heard of firewalls, and anyone who’s ever dealt with IT security must have configured a software firewall at some stage. There’s much variety among available firewall systems, but of course from a technical point of view the crucial factor is the functionality they offer. Commercial firewall vendors will frequently do their best to convince you that their software offers unique and highly advanced features that the competition can only dream of, provides near-infinite filtering capabilities and comes with an inexhaustible supply of helpful and cheerful round-the-clock support.

The problem is that support does indeed tend to be necessary, even though a working product would be much preferable to delightful e-mail exchanges with the support staff. More often than not we would also like to see and understand why something is not working, as it soon turns out that a chosen product’s capabilities are not only finite, but frequently insufficient. The good news is that if you need unique and advanced functionality, you can develop it yourself using open source software such as IPTables, a bit of spare time and (hopefully) this article. (Which of course is not to say that commercial packages are useless).

You can download the IPTables package at www.netfilter.org, and it is supplied as standard with Linux distributions with kernel 2.4 and later. The website also provides a brief overview of the project’s history - it’s been around long enough to be considered a mature and reliable product. In my opinion, one of the package’s most powerful features is its support for custom extensions.

The Netfilter package on which IPTables is based is actually a development framework for filtering and modifying network packets. From a programmer’s point of view, its key features are:

  • hooks - specific locations in the system’s network protocol stack from which Netfilter code is called for each packet that traverses the stack,

  • functions assigned to particular hooks and called by Netfilter for each packet that traverses the protocol stack,

  • the ip_queue driver for queuing packets into user space for asynchronous processing.

The source code is extensively commented - a non-functional, but equally useful feature.

IPTables is essentially a set of modules that use Netfilter functionality to define rule tables, pattern-based packet matching criteria and actions to be taken for matching packets. All this allows Netfilter functionality to be accessed at a higher level of abstraction for clearer and more convenient use. The name IPTables originates from the fact that rule lists are represented as tables, and each named table is stored in memory.

IPTables can generally be divided into two functional components: one for port and network address translation services (PNAT) and the other for filtering services. Both components are extensible. Apart from extension modules, the package also includes user space tools that allow rules to be entered more conveniently.

Extension modules

An IPTables extension module is a typical kernel module that has to use a number of standard structures and implement several module functions. The module code also has to be re-entrant, as it is possible for its execution to be interrupted during processing by a request to process another packet. In SMP systems with large numbers of processors, the chances of interruption are much larger. Here’s a list of the basic functions that a module has to implement:

  • init_module() - module entry point, which has the basic task of registering the module within the framework, returning 0 if successful or a negative integer in case of failure,

  • cleanup_module() - module exit point; the function code should unregister the module from the Netfilter framework,

  • ipt_register_match() - used to register match extensions; takes a struct ipt_match structure as a parameter,

  • ipt_register_target() - used to register target extensions; takes a struct ipt_target structure as a parameter,

  • ipt_unregister_match() - unregisters a match extension,

  • ipt_unregister_target() - unregisters a target extension.

A module is usually used either as a target module or as a matching module, so you need to select the four relevant functions from the collection above. In fact, in actual implementations the function names are a little different, and we can also make use of standard macros. The structures passed to ipt_register_match() and ipt_register_target() are described in the Inset Extension registration structures.

Apart from the functions listed above, we also need to implement extension-specific functions, whose pointers were passed in the module registration structure. Once that’s done, we have a working module. Let’s see how all this works in practice.

Extension registration structures

Depending on the extension type (match or target), registration is done by calling one of ipt_register_match() or ipt_register_target(). Each function takes a suitable parameter structure (though the two structure types are similar). The structure fields to be filled in are:

  • name - extension module name, preferably included in the module file name (extension module files usually follow the naming convention ipt_NAME.o),

  • me - a field that should be filled with the THIS_MODULE literal to indicate the current module; the field is used by the module reference counter and therefore also the cleanup_module() function,

  • checkentry - pointer to a function that is called whenever a rule is added for the current module; the function should check the validity of the new rule,

  • destroy - optional pointer to a function that should be called when removing an entry of the current extension type,

  • match or target (depending on extension type) - the most important piece of information: a pointer to a function that matches packets or performs specific operations on matched packets.

The match structure is called struct ipt_match and the target structure is struct ipt_target.

Implementing a sample extension

Getting back to commercial products for a while, many of them provide filtering functions that are grouped into packages and accessible through a few clicks within the GUI. A certain commercial solution provides a set of simple operations collectively called Fingerprint Scrambling, which are supposed to hinder operating system fingerprinting. We will implement similar functionality using the ipt_TTL, ipt_IPID and ipt_ISN extensions. The first modifies the TTL field of an IP datagram and is available in the standard package. We will go through creating the second extension below, and developing the third one will be left as homework for the Reader.

Operating system fingerprinting

Any attack has to preceded by reconnaissance to gather information about the target. For attacks in the computer world, vital information includes the operating system type and version, and target application versions. Even without local access to the target host, we can gather information through the analysis of network packets received from the target system - a process commonly known as OS fingerprinting.

Fingerprinting can be divided into passive and active methods. Passive fingerprinting is limited to listening for packets sent by the target system, while active fingerprinting additionally involves provoking the target to respond by sending it various queries, attempting to start a TCP session etc.

Once received, the packets are analysed to see how both obligatory and optional protocol functionality is implemented by the sender system, and the correlated information is used to determine the most likely system type and version.

The name of the ipt_IPID extension is less than obvious, so let’s discuss its purpose first. One of the factors that aid remote OS fingerprinting is the ability to identify the algorithm used to calculate the value of the ID field in IP datagrams sent by the remote system (you can find more information in the Inset ID field in IP datagrams). Our job will be to modify the ID field in outgoing datagrams so as to hinder such identification.

ID field in IP datagrams

Each IP datagram header has an ID field, which is used when reassembling fragmented datagrams. Fragments that are part of the same datagram have the same unique ID - a 16-bit word, theoretically allowing up to 65536 packets fragments to be supported by the same node at any one time. If no fragmentation takes place, the ID field is basically unused, but operating systems still have to calculate its value for each packet and use a variety of algorithms for doing so. Some implementations increment the value by a constant for each datagram, others increment it by a random value from a limited range, and still others simply pick a random number for each datagram.

Other system-dependent subtleties also influence the ID value. A characteristic feature of some older operating systems is that the ID is always 0 for datagrams with the DF (Don’t Fragment) bit set, while recent Linux kernel versions always set the ID to 0 for TCP SYN/ACK packets. And these are just a few of the many implementation-specific features.

Implementation differences make the ID field very useful for fingerprinting, and it is analysed both by active scanners (such as nmap) and passive scanners (such as p0f). To determine the algorithm used by the target system, simply run nmap with the -v and -O options. If subsequent IP datagrams for a target system have predictable ID values, the system can be exploited to scan other networks (including ones inaccessible from the outside - the technique is called an idlescan and can also be performed using nmap (using the -sI options).

ipid_checkentry

As already mentioned, IPTables extension modules are registered by passing a special structure containing several function pointers. Let’s start by looking at the structure’s checkentry field.

The function indicated by the pointer supplied in this field is called for each rule that uses the relevant extension. Its entry parameters are:

  • the name of the table the rule is added to,

  • the rule entry itself, supplied as an ipt_entry structure,

  • extension-specific options,

  • a mask specifying the hooks for which the rule can be called.

A value of 0 is returned if the rule cannot be accepted, and 1 is returned otherwise.

At this stage we can therefore check if the rule is being added to the right table. Rules that modify packets should be added to the mangle table. This includes our rule, so can simply check if the table name is correct. We can also check the validity of module-specific options, for example verifying whether they fall in a valid range. If a rule is intended for a specific protocol only, we can also check the protocol type. Protocol information can be found in the ipt_entry structure.

It is good practice to check whether a structure containing extension-specific parameters is properly aligned in memory - the macro to do this is IPT_ALIGN. Our module is fairly simple, so all we need to check is the memory alignment and the name of the table the rule is added to. Listing 1 presents the function code.

Listing 1. Code for the ipid_checkentry function

static int ipid_checkentry(const char *tablename,

const struct ipt_entry *e, void *targinfo,

unsigned int targinfosize, unsigned int hook_mask) {

if(strncmp(tablename, "mangle", 6) != 0) {

printk(KERN_WARNING "IPID: Can only be called from the "mangle" table");

return 0;

} if(targinfosize != IPT_ALIGN(sizeof(struct ipt_ipid_target_info))) {

printk(KERN_WARNING "IPID: targinfosize %u != %Zun",

targinfosize, IPT_ALIGN(sizeof(struct ipt_ipid_target_info)));

return 0;

} return 1;

}

ipid_destroy

The function supplied in the destroy field is called whenever a rule that uses the extension is removed from memory. This makes it possible to allocate space for rule data in the checkentry function and release it in the destroy function. In our case the function is empty, so its code will simply be:

static void ipid_destroy(void *targinfo, unsigned int targinfosize) {}

ipid_target

Time to have a look at the function that actually processes packets. As described above, we can specify either a match or target function. In our example we’re just modifying the packet and leaving the job of matching it to other extensions, so we will use target. A target function takes several parameters, including a pointer to a socket buffer (skb structure), the input and output interface name for the packet (one of these can be empty) and rule-specific data. The data is taken from user space and supplied when the rule is added, potentially containing extension-specific options or temporary rule data. We will discuss options and data storage later on, so we’ll skip the structure for now.

The socket buffer structure is described in detail in the Inset Socket buffer - all we need to know at this stage is that it’s a universal Linux kernel structure to allow easy packet manipulation from all layers of the protocol stack. The socket buffer provides a central structure for storing the majority of packet-related information, which will be most useful in developing our extension.

Apart from suitably modifying packet content, the function also has to inform the IPTables framework what should be done with the packet. For simple targets such as ACCEPT or DROP, there is usually just one verdict. For match functions, the verdict usually specifies whether the packet has been matched or not (although packet dropping is also possible in error situations).

Our function modifies the packet, so IPTables should be instructed to accept the packet for further processing. In specific error situations (such as insufficient memory to process the packet), we will order the packet to be dropped. The list of possible verdicts for target functions is short, but quite sufficient:

  • NF_DROP - drop the packet,

  • NF_ACCEPT - accept the packet for further processing,

  • NF_STOLEN - informs Netfilter that the packet along with its entire sk_buff has been taken over by the module,

  • NF_QUEUE - verdict used by Netfilter’s ip_queue module (among others) to queue packets for processing in user space,

  • NF_REPEAT - causes the packet to be re-run through all the functions registered for the current hook.

All these are Netfilter verdicts that we can use, but since we’re working on a higher level of abstraction (at the IPTables level), we will use the IPT_CONTINUE verdict, which instructs the framework to continue packet processing and is used by IPTables extensions.

Now we know what the function parameters are, we can set about implementing the function body. As specified, we will be modifying the value of the ID field in the IP protocol header. To start with, we will use a simple algorithm with an internal postincremented counter. However, regardless of the actual method, we will be changing the IP header and therefore the content of the socket buffer (struct sk_buff), so we need to notify the system of our intentions. In 2.6 series kernels, this can be done using a single statement:

if (!skb_ip_make_writable(pskb, (*pskb)->len))

return NF_DROP;

We call skb_ip_make_writable() passing it the buffer and buffer length. Here we also see one situation where we can order the packet to be dropped - if we cannot process the packet as required, it will be safer to drop it. In 2.4 series kernels, the system is notified of the modification by copying the buffer:

struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);

What we actually to with the data in the buffer (and therefore in the protocol headers) depends on the purpose of the extension and on our own inventiveness. The operations are typically very simple - if no modifications are made, the verdict can often be determined using a simple comparison. In our case, however, the packet data is modified, which additionally requires packet checksums to be updated - the easiest way of doing this is to use the standard functions sum_fold() and csum_partial(). The code in Listing 2 illustrates the process.

The Netfilter framework should also be notified that changes have been made to the packet, which is done by setting the relevant flag in the the socket buffer structure:

(*pskb)->nfcache |= NFC_ALTERED;

A typical requirement for custom extensions is that the extension module should write notifications to log files. For performance reasons, logging should not use too much by way of host resources, so the number of log writes can be limited using net_ratelimit(). A typical log write call should therefore be something like:

if(net_ratelimit()) printk("message...\n");

For our first implementation, a simple text message should suffice. Listing 2 presents a sample implementation of the target function.

Once the functions are implemented, we just need to supply a structure filled with the relevant data, register it, add a few includes and the extension module is ready.

Socket buffer

Apart from user data, network packets also contain protocol headers. Each protocol stack layer from the transport layer down prepends its own header to the packet, with the particular layers and protocols being served by different functions. To avoid having to copy header information all over the place, a central data structure (a struct sk_buff) is used to store data about all protocol headers. The structure contains the following information:

  • packet arrival time (for incoming packets),

  • the network interface that received the packet,

  • checksums,

  • the network socket (if the packet is bound to a local socket),

  • a variety of other data used during packet processing throughout the protocol stack.

The same structure is used by Netfilter and passed to match and target functions. Several utility functions exist, for example for copying the structure or making it writeable. You can find a detailed description of the sk_buff fields in the skbuff.h header file.

Listing 2. Sample implementation for the ipid_target extension

static unsigned int ipid_target(struct sk_buff **pskb,

const struct net_device *in, const struct net_device *out,

unsigned int hooknum, const void *targinfo, void *userinfo) {

struct iphdr * iph;

u_int16_t ipid_diffs[2];

if (!skb_ip_make_writable(pskb, (*pskb)->len))

return NF_DROP;

iph = (*pskb)->nh.iph;

ipid_diffs[0] = (iph->id)^0xffff;

ipid_diffs[1] = iph->id = htons(counter++);

iph->check = csum_fold(csum_partial((char *)ipid_diffs,

sizeof(ipid_diffs), iph->check^0xffff));

(*pskb)->nfcache |= NFC_ALTERED;

return IPT_CONTINUE;

}

User space utility

Once the extension is ready, we need to provide a way of adding IPTables rules that use it. The rules will be added using the standard iptables utility, which is also modular, so adding support for our module is a matter of developing and supplying a suitable library.

The library has to provide the _init() function, from which the register_match() or register_target() functions will called, depending on the module type to be registered - very similar to registering an extension. In our case, we will use register_target(), passing it an argument structure. The most important structure fields are:

  • next - used for creating target lists, for example when listing rules. The initial value should be NULL,

  • name - a name logically related to the library file name, for example IPID for libipt_IPID.so,

  • version - iptables version,

  • help - pointer to a function that displays syntax help for the extension,

  • init - pointer to an optional initialisation function, which will be executed before the function indicated by parse,

  • parse - pointer to a function that parses any parameters unsupported by IPTables itself. If valid options for the extension are supplied, the function should return a non-zero value. One of the entry parameters is invert, which is set to true if the option specification was preceded by a ! character,

  • final_check - pointer to a function that will be called after extension-specific options have been parsed, for example allowing exit_error() to be called if contradictory options are supplied or an obligatory option is missing,

  • print - pointer to a function used for displaying non-standard information for a given rule; called when rules are listed using the iptables -L command,

  • save - pointer to a function used to serialise a rule from memory into a format that can be stored and recreated later,

  • extra_opts - pointer to a table of structures that constitutes a list of extra options accepted by the extension (the list should be terminated by a structure filled with NULLs); the list is merged with the list of standard arguments and passed to getopt_long().

The structure for match extensions is exactly the same. All we need to do to test and run our module is fill in the argument structure. For this example, we will declare the functions as bodiless stubs, and the options list will contain just one record filled with NULLs. Place the compiled library in a location accessible to iptables and we can go on to the testing phase.

Testing

The stability of a kernel module can have an impact on the stability of the kernel it is loaded into, so it will be safer to test our module in an isolated sandbox environment. The nfsim utility, also available from the www.netfilter.org website, is an emulator for the Netfilter framwork, and we can use it to safely test new extensions.

Once the emulator is running, we are provided with a console for entering IPTables rules, generating network packets and simulating TCP sessions. The effects of operations within the simulator are displayed on the screen, along with information about each packet’s progress through the protocol stack and about its final fate. We will also see any messages returned by the module via the printk() function.

Our extension module should be placed in the netfilter/ipv4 directory so it is automatically loaded when the emulator is started. A brief emulator session is usually enough to discover any critical errors, check that the module does what it should, verify the checksums etc. The built-in help system (accessed using the help command) is quite sufficient to learn how to use the emulator, so I will not dwell on the subject here.

Figure 1 presents a typical emulator session. As you can see, the module was loaded without problems, and its rule was added correctly. Two packets were then sent. The IPID in the first one was changed from 0 to 0, and in the second one it was changed from 0 to 1. That’s because the IPID is always 0 when the packet is generated, but our module keeps an internal counter that is used as the new IPID and incremented each time the module is run. Note where in the call chain our module comes - the rule was added to the FORWARD chain’s mangle table, so the module message appears before the end of the chain.

Once the extension has been tested in the emulator, we can try to load the module into a real kernel. So far everything has been working fine, so we can assume that the alpha release has passed informal practical testing.

Figure 2 shows four icmp echo-request packets captured using tcpdump. Each packet appears twice: before and after filtering by the firewall. You can see the TTL decremented by 1 (which is as it should be) and a change in the ID, which was introduced by our module - it only changes by 1, while the original ID changes by more than 1.

Figure 1. Typical session in the nfsim emulator

Figure 2. Packet IDs modified by the firewall

Freedom of choice

Changing the ID as described above might occasionally be enough to hide our system’s identity, but it won’t always be enough. Besides, if we wanted to introduce changes to the ID modification method for the current module version, we would have to change the source code and recompile the entire module. And what if we wanted different changes to be made by two different rules that both use our module? All this can be done by passing parameters from user-space to the module via the iptables utility. The code to do this is very simple.

Extension options are passed as a list of structures (as usual for getopt()) to the extension’s library file, for example:

static struct option opts[] = {

{"random", 0, 0, 'r'},{"incremental", 1, 0, 'i'},{0}

};

If you’ve ever used the getopt_long() function, you’re no doubt familiar with the parameter structure fields. If not, refer to the function’s man page for a detailed description. Once added, the options have to be processed within the parse() function specified for the extension library, again in typical getopt_long() fashion.

Now for the structure to be passed to the kernel. It is, in fact, the same structure whose alignment we checked in ipid_checkentry(). For each extension, we need to define a structure associated with the current rule and used to pass its options. Each time we need to call a match or target function for an extension, the same structure will be passed.

However, the structure’s lifetime actually begins when the rule is added in user space. The structure can be accessed from the parse() function by dereferencing it from the ipt_entry_target structure, as shown in Listing 3. The listing also contains the structure definition.

The flags variable is another important parameter for the parse() function, allowing information to be passed to final_check() and between subsequent parse() calls. We will use it to inform final_check() which parameters were supplied by the user. In this case, the user has to specify exactly one algorithm for modifying the ID field in IP datagrams. Listing 3 presents the sample code. It’s also worth implementing the help(), print() and save() functions so they provide the required functionality, but that’s left as an exercise to the Reader.

Listing 3. Implementing extension parameters for an IPTables extension library

struct ipt_ipid_target_info {         u_int32_t mode;

u_int32_t step;

};

static int parse(int c, char **argv, int invert, unsigned int *flags,

const struct ipt_entry *entry, struct ipt_entry_target **target) {

struct ipt_ipid_target_info * ipid_info = (struct ipt_ipid_target_info *)(*target)->data;

...

*
flags = ipid_info->mode;

return 1;

} static void final_check(unsigned int flags) {

if(!(flags&IPID_MODE_RANDOM) && !(flags&IPID_MODE_INCREMENTAL))

exit_error(PARAMETER_PROBLEM, "You have to choose an algorithmn");

...

}

It is also good practice to use the module’s checkentry() function to verify that the user-supplied options are valid in the first place, but we will not go into it here.

To use one of the options from a kernel module, we can simply access the structure passed into checkentry() and target() or match() functions. The structure is accessed in a similar way as for user space operations.

Listing 4 presents sample code with modified snippets of the ipid_target() function. After recompiling the module and the library, we will be able to specify the ID modification method from the command line.

Listing 4. Accessing extension parameters from kernel-level code

static unsigned int ipid_target(struct sk_buff **pskb,

...

struct ipt_ipid_target_info * ipid_info = (struct ipt_ipid_target_info *)targinfo;

...

if(ipid_info->mode&IPID_MODE_INCREMENTAL) { ipid_diffs[1] = iph->id = htons(counter);

counter += ipid_info->step;

} else if(ipid_info->mode&IPID_MODE_RANDOM) { get_random_bytes(&(ipid_diffs[1]), sizeof(u_int16_t));

ipid_diffs[1] = iph->id = htons(ipid_diffs[1]);

} ...

Data storage

In our example, the counter we use for modifying the ID value is stored globally. One problem with this approach is that the counter value is shared by all rules that use the module. It would be more useful to keep a separate counter for each rule, which would for example allow packet IDs to be changed randomly for one network and incrementally for another. How can this be done?

As already mentioned, each call to ipid_target() requires a targinfo structure to be passed, and the definition of this structure is entirely up to the programmer. We could therefore add an extra field to the structure to represent the counter - a separate structure instance is created for each rule, so we would have a separate counter for each rule. The modified counter update code in the ipid_target() function could then be:

if(ipid_info->mode&IPID_MODE_INCREMENTAL) {

ipid_diffs[1] = iph->id = htons(ipid_info->lastval);

ipid_info->lastval += ipid_info->step;

}

However, in solving one problem, we’ve created another. In SMP systems, each processor will get its own copy of the table, so we will have to deal with multiple counter copies. We cannot allow the same value to be repeated, so we need to ensure that only one copy of the counter can exist, regardless of the number of processors.

One of the simpler ways of solving the problem is to add an extra field to the targinfo structure, containing a pointer to the master copy of the structure. We can then use the pointer to reference fields whose values are modified. Adding this to the code requires only cosmetic changes - apart from adding the extra field to targinfo, we just need to add the following assignment to the ipid_checkentry() function:

ipid_info->master = ipid_info;

and change all references to the last counter value within ipid_target from:

ipid_info->lastval

to:

ipid_info->master->lastval

Apart from the tables being copied, we also have one other problem to solve: the module code has to be re-entrant, as it is possible for packet processing to be interrupted by a request to process another packet. Wherever concurrent data access appears, the programmer has to manage it to ensure data consistency. Suppose we have two packets being processed concurrently, and the counter value is read for both of them before the counter is incremented - such behaviour would result in two packets having the same IDs, and other inconsistencies might appear as well.

An easy solution to the problem is to protect the counter using a spinlock_t. Spin locks are a kernel-level synchronisation mechanism. To use a lock, we start by declaring it:

static spinlock_t ipid_lock = SPIN_LOCK_UNLOCKED;

From now on, whenever we refer to a shared value, we can lock the spin lock before accessing the value, using a function call like:

spin_lock_bh(&ipid_lock);

and remove the lock once the protected operation is complete:

spin_unlock_bh(&ipid_lock);

Locking and unlocking calls should be coordinated so as to prevent a situation where one thread wants to get a lock, but has to wait forever because another thread is waiting to remove the lock. This would result in a deadlock situation, leaving the entire system dead as a dodo.

You might think we’ve addressed all possible data storage requirements for our extension, but in fact you don’t have to look far for a situation where they won’t be sufficient. Suppose we wanted to keep separate counters for each IP stream, defined as a (source, target) pair. We would then most likely need to create more elaborate data structures - while it would theoretically be possible to introduce a new rule for each pair, this would be highly inefficient.

In such cases, you might consider creating custom object caches and using more elaborate data structures for your modules (hash tables, trees, lists), but that’s a story for another article altogether.

The end is just the beginning

With the information provided in this article and a bit of time, you should now be able to write a simple but fully functional extension module, and have a solid basis for exploring the subject on your own and writing some more advanced modules.

The IPTables package comes with numerous modules which have not been mentioned here and make use of more advanced framework functionality, such as connection tracking, filtering for protocols that use parallel connections, and advanced network address translation.

The ipt_IPID module we’ve just completed can be used as one of the components of a firewall installation to conceal the true identity of protected operating systems. While on its own it is obviously quite insufficient to protect a system from fingerprinting (just like the Fingerprint Scrambling function of a certain commercial firewall solution), it can be quite effective in preventing the protected hosts from being used to explore the network.

Remember, though, that the ID field is crucial for fragmented IP packets, so it cannot be modified for packets that are actually datagram fragments. This means that the rule should be skipped for fragmented packets, which can be done for example by writing a suitable match extension.

Developing the ipt_ISN module mentioned earlier in the article is left as a longer exercise for the reader, as it requires several new problems to be solved, including storing state information for TCP connections and ensuring bilateral sequence number modification (SEQ packets are affected in one direction and ACK packets in the other). However, the problems can quite definitely be solved, which I can personally vouch for since I actually created such a module while working on this article.

 

 

 

 

 

 

 

 

 

 



Web Design Services